<?php
/**
 * This file defines the classes {@see \BF\DateTime}, {@see \BF\Bundesland} and {@see \BF\Feiertage}.
 *
 * @author     Kado <ulfikado@gmail.com>
 * @category   Beluga PHP5.3+ Framework
 * @package    BF\Core
 * @since      2015-01-17 14:29
 * @version    0.1
 */

namespace BF
{

   # <editor-fold defaultstate="collapsed" desc="CLASS '\BF\DateTime'">

   /**
    * This class defines a object for better date time handling.
    *
    * If loaded, the class uses the INTL PHP extension for localized outputs. Otherwise the normal date time functions
    * like strftime(), mktime() etc. are used.
    *
    *@property      integer      $Year                 The year of current DateTime.
    * @property      integer      $Month                The month of current DateTime.
    * @property      integer      $Day                  The day of month of current DateTime.
    * @property      integer      $Hour                 The hours of current DateTime.
    * @property      integer      $Minute               The minutes of current DateTime.
    * @property      integer      $Second               The seconds of current DateTime.
    * @property-read integer      $DayOfWeekNumber      The day of week number (0-6 Su-Sa)
    * @property-read integer      $Timestamp            The UNIX Timestamp of current DateTime.
    * @property-read \BF\DateTime $Date                 The Date, reduced to the time 00:00:00
    * @property-read integer      $DayOfYear            The day of the current instance year.
    * @property-read integer      $DaysOfYear           The max count of days of current defined year.
    * @property-read integer      $DaysOfMonth          The max day number of current defined month.
    * @property-read integer      $Quarter              Return the quarter (1-4)
    * @property-read string       $TimezoneOffsetRFC822 The timezone offset by RFC822 (e.g.: -0800)
    * @property-read string       $TimezoneOffsetGMT    The GMT timezone offset. (e.g.: -08:00)
    * @property-read string       $TimezoneName         The timezone name. (e.g.: Europe/Berlin)
    * @property-read string       $TimezoneNameShort    The short, abbr. timezone name. (e.g.: CET)
    * @property-read boolean      $IsLeapYear           Returns if it its a leap year.
    * @property-read string       $MonthName            Returns a localized month name,
    *                                                   depending to currently used locale by {@see \BF\Locale}
    * @property-read string       $ShortMonthName       Returns a localized short month name (3 characters long),
    *                                                   depending to currently used locale by {@see \BF\Locale}
    * @property-read string       $WeekDayName          Returns a localized week day name,
    *                                                   depending to currently used locale by {@see \BF\Locale}
    * @property-read string       $ShortWeekDayName     Returns a localized week day name (2 characters long),
    *                                                   depending to currently used locale by {@see \BF\Locale}
    * @since         v0.1
    */
   class DateTime
   {

      # <editor-fold defaultstate="collapsed" desc=" - - -   P R I V A T E   F I E L D S   - - - - - - - - - - - - - - - - - - - - - -">

      /**
       * The based unix timestamp
       *
       * @var integer
       */
      private $unixStamp;

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - >   P U B L I C   C O N S T R U C T O R   - - - - - - - - - - - - - - - - - -">

      /**
       * Inits a new instance. If no parameter is defined, the current timestasmp is used.
       *
       * @param int $timestamp Unix-Timestamp (default=null means now)
       */
      public function __construct( $timestamp = null )
      {
         $this->unixStamp = \is_integer( $timestamp ) ? $timestamp : \time();
      }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - -   P U B L I C   M E T H O D S   - - - - - - - - - - - - - - - - - - - - - -">

      # <editor-fold defaultstate="collapsed" desc=" * *   G E T T E R S   * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *">

      /**
       * Returns the based unix timestamp
       *
       * @return integer
       */
      public final function getTimestamp()
      {

         return $this->unixStamp;

      }

      /**
       * Returns the year (4 digits).
       *
       * @return integer
       */
      public final function getYear()
      {
         return \intval( \strftime( '%Y', $this->unixStamp ) );
      }

      /**
       * Returns the month number (1-12).
       *
       * @return integer
       */
      public final function getMonth()
      {
         return \intval( \strftime( '%m', $this->unixStamp ) );
      }

      /**
       * Returns the day of the month (1-31).
       *
       * @return integer
       */
      public final function getDay()
      {
         return \intval( \strftime( '%d', $this->unixStamp ) );
      }

      /**
       * Returns the hour (0-23)
       *
       * @return integer
       */
      public final function getHour()
      {
         return \intval( \strftime( '%H', $this->unixStamp ) );
      }

      /**
       * Returns the minutes (0-59)
       *
       * @return integer
       */
      public final function getMinute()
      {
         return \intval( \strftime( '%M', $this->unixStamp ) );
      }

      /**
       * Returns the seconds (0-59)
       *
       * @return integer
       */
      public final function getSecond()
      {
         return \intval( \strftime( '%S', $this->unixStamp ) );
      }

      /**
       * Returns the day of week number (0-6)
       *
       * @return integer
       */
      public final function getDayOfWeekNumber()
      {
         if ( \MOD_INTL )
         {
            $fmt = \IntlDateFormatter::create(
               (string) \BF\Locale::GetInstance(), \IntlDateFormatter::SHORT,
               \IntlDateFormatter::SHORT, \date_default_timezone_get(),
               \IntlDateFormatter::TRADITIONAL, 'e');
            return \intval( $fmt->format( $this->unixStamp ) );
         }
         if ( false === ( $res = \strftime( '%u', $this->unixStamp ) ) )
         {
            $res = \strftime( '%w', $this->unixStamp );
         }
         return \intval( $res );
      }

      /**
       * Returns the quarter (1-4)
       *
       * @return integer
       */
      public final function getQuarter()
      {
          if ( \MOD_INTL )
          {
              $fmt = \IntlDateFormatter::create(
                  (string) \BF\Locale::GetInstance(), \IntlDateFormatter::SHORT,
                  \IntlDateFormatter::SHORT, \date_default_timezone_get(),
                  \IntlDateFormatter::TRADITIONAL, 'Q');
              return \intval( $fmt->format( $this->unixStamp ) );
          }
          $month = $this->getMonth();
          return ($month < 4) ? 1 : ($month < 7 ? 2 : ($month < 10 ? 3 : 4));
      }

      /**
       * Returns the timezone offset by RFC822 (e.g.: -0800)
       *
       * @return string
       */
      public final function getTimezoneOffsetRFC822()
      {
         return \date( 'O', $this->unixStamp );
      }

      /**
       * Returns the GMT timezone offset. (e.g.: -08:00)
       *
       * @return string
       */
      public final function getTimezoneOffsetGMT()
      {
         return \date( 'P', $this->unixStamp );
      }

      /**
       * Returns the timezone name. (e.g.: Europe/Berlin)
       *
       * @return string
       */
      public final function getTimezoneName()
      {
         return \date( 'e', $this->unixStamp );
      }

      /**
       * Returns the short, abbr. timezone name. (e.g.: CET)
       *
       * @return string
       */
      public final function getTimezoneNameShort()
      {
         return \date( 'T', $this->unixStamp );
      }

      /**
       * Returns if it its a leap year.
       *
       * @return boolean
       */
      public final function getIsLeapYear()
      {
         return (bool) \intval( \date( 'L', $this->unixStamp ) );
      }

      /**
       * Returns the max count of days of current defined year.
       *
       * @return integer
       */
      public final function getDaysOfYear()
      {
         return $this->getIsLeapYear() ? 366 : 365;
      }

      /**
       * Returns the max day number of current defined month.
       *
       * @return integer
       */
      public final function getDaysOfMonth()
      {
         return \intval( \date( 't', $this->unixStamp ) );
      }

      /**
       * Returns the day number in currently defined year.
       *
       * @return integer
       */
      public final function getDayOfYear()
      {
         return \intval( \date( 'z', $this->unixStamp ) );
      }

      /**
       * Returns the date part of current instance, reduced to the time 00:00:00 as a new \BF\DateTime instance.
       *
       * @return \BF\DateTime
       */
      public final function getDate()
      {
         return self::Create( $this->getYear(), $this->getMonth(), $this->getDay() );
      }

      /**
       * Clones the current instance.
       *
       * @return \BF\DateTime
       */
      public function  __clone()
      {
         return new \BF\DateTime( $this->unixStamp );
      }

      /**
       * Gibt den naechsten deutschen Feiertag fuer ein Bundesland zurueck. Diese
       * Informationen werden als Array zurueckgegeben.
       *
       * (0=DifferenzTage, 1=FeitertagsTimestamp, 2=FeiertagsName)
       * 0 ist von Type Double.
       *
       * @param integer $region \BF\Bundesland (\BF\Bundesland::SACHSEN z.B.)
       * @return array
       */
      public final function getNextGermanHolyday( $region )
      {
         $ftage = \BF\Feiertage::AlleDeutschen( $region, $this->getYear() );
         foreach ( $ftage as $stamp => $name )
         {
            if ( $stamp < $this->unixStamp )
            {
               continue;
            }
            $curr = array(
                ( $stamp - $this->unixStamp ) / 86400,
                $stamp,
                $name
            );
            return $curr;
         }
         # Im Aktuellen Jahr kein Feiertag mehr, also naechster am 1.Januar des Folgejahres
         $stamp = \mktime( 0, 0, 0, 1, 1 , $this->getYear() + 1 );
         $curr = array(
            ( $stamp - $this->unixStamp ) / 86400,
            $stamp,
            'Neujahr'
         );
         return $curr;
      }

      /**
       * Returns a localized month name, depending to currently used locale by {@see \BF\Locale}
       *
       * @return string
       */
      public final function getMonthName()
      {
         switch ( $this->getMonth() )
         {
            case 1: return \BF\_0( '_DateTime', 'January' );
            case 2: return \BF\_0( '_DateTime', 'February' );
            case 3: return \BF\_0( '_DateTime', 'March' );
            case 4: return \BF\_0( '_DateTime', 'April' );
            case 5: return \BF\_0( '_DateTime', 'May' );
            case 6: return \BF\_0( '_DateTime', 'June' );
            case 7: return \BF\_0( '_DateTime', 'July' );
            case 8: return \BF\_0( '_DateTime', 'August' );
            case 9: return \BF\_0( '_DateTime', 'September' );
            case 10: return \BF\_0( '_DateTime', 'October' );
            case 11: return \BF\_0( '_DateTime', 'November' );
         }
         return \BF\_0( '_DateTime', 'December' );
      }

      /**
       * Returns a localized short month name (3 characters long), depending to currently used locale by {@see \BF\Locale}
       *
       * @return string
       */
      public final function getShortMonthName()
      {
         switch ( $this->getMonth() )
         {
            case 1: return \BF\_0( '_DateTime', 'Jan' );
            case 2: return \BF\_0( '_DateTime', 'Feb' );
            case 3: return \BF\_0( '_DateTime', 'Mar' );
            case 4: return \BF\_0( '_DateTime', 'Apr' );
            case 5: return \BF\_0( '_DateTime', 'May' );
            case 6: return \BF\_0( '_DateTime', 'Jun' );
            case 7: return \BF\_0( '_DateTime', 'Jul' );
            case 8: return \BF\_0( '_DateTime', 'Aug' );
            case 9: return \BF\_0( '_DateTime', 'Sep' );
            case 10: return \BF\_0( '_DateTime', 'Oct' );
            case 11: return \BF\_0( '_DateTime', 'Nov' );
            case 12:
            default: return \BF\_0( '_DateTime', 'Dec' );
         }
      }

      /**
       * Returns a localized week day name, depending to currently used locale by {@see \BF\Locale}
       *
       * @return string
       */
      public final function getWeekDayName()
      {
         switch ( $this->getDayOfWeekNumber() )
         {
            case 0: return \BF\_0( '_DateTime', 'Sunday' );
            case 1: return \BF\_0( '_DateTime', 'Monday' );
            case 2: return \BF\_0( '_DateTime', 'Tuesday' );
            case 3: return \BF\_0( '_DateTime', 'Wednesday' );
            case 4: return \BF\_0( '_DateTime', 'Thursday' );
            case 5: return \BF\_0( '_DateTime', 'Friday' );
            case 6:
            default: return \BF\_0( '_DateTime', 'Saturday' );
         }
      }

      /**
       * Returns a localized week day name (2 characters long), depending to currently used locale by {@see \BF\Locale}
       *
       * @return string
       */
      public final function getShortWeekDayName()
      {
         switch ( $this->getDayOfWeekNumber() )
         {
            case 0: return \BF\_0( '_DateTime', 'Su' );
            case 1: return \BF\_0( '_DateTime', 'Mo' );
            case 2: return \BF\_0( '_DateTime', 'Tu' );
            case 3: return \BF\_0( '_DateTime', 'We' );
            case 4: return \BF\_0( '_DateTime', 'Th' );
            case 5: return \BF\_0( '_DateTime', 'Fr' );
            case 6:
            default: return \BF\_0( '_DateTime', 'Sa' );
         }
      }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" * *   S E T T E R S   * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *">

      /**
       * Sets a new time. The date part will be left unchanged.
       *
       * @param  integer|array $hour   The hours to set (if NULL is defined here, the original hours are used)
       *                               Alternative, also a array can be used. It must use the keys 'hour', 'minute'
       *                               and 'second' or must be numeric indicated (hour, minute, second)
       * @param  integer       $minute The minutes to set (if NULL is defined here, the original minutes are used)
       * @param  integer       $second The seconds to set (if NULL is defined here, the original seconds are used)
       */
      public final function setTime( $hour = 0, $minute = 0, $second = 0 )
      {

         if ( \is_array( $hour )
           && \count( $hour ) > 0 )
         {
            // $hour is a array with time informations

            // Change all array keys to lower case
            $array = \array_change_key_case( $hour, \CASE_LOWER );

            if ( isset( $array[ 'hour' ] ) )
            {
               $this->setTime(
                  \intval( $array[ 'hour' ] ),
                  isset( $array[ 'minute' ] ) ? \intval( $array[ 'minute' ] ) : 0,
                  isset( $array[ 'second' ] ) ? \intval( $array[ 'second' ] ) : 0
               );
            }
            else
            {
               $array = \array_values( $array );
               $this->setTime(
                  \intval( $array[ 0 ] ),
                  isset( $array[ 1 ] ) ? \intval( $array[ 1 ] ) : 0,
                  isset( $array[ 2 ] ) ? \intval( $array[ 2 ] ) : 0
               );
            }

            return;

         }

         if ( \is_null( $hour ) )
         {
            $hour = $this->getHour();
         }
         if ( \is_null( $minute ) )
         {
            $minute = $this->getMinute();
         }
         if ( \is_null( $second ) )
         {
            $second = $this->getSecond();
         }
         $this->unixStamp = \mktime(
            $hour,
            $minute,
            $second,
            $this->getMonth(),
            $this->getDay(),
            $this->getYear()
         );
      }

      /**
       * Sets a new date. The time part will be left unchanged.
       *
       * @param  integer|array $year  The year to set. (If here NULL is used, the year will be unchanged)
       *                              Alternative, also a array can be used. It must use the keys 'year', 'month'
       *                              and 'day' or must be numeric indicated (year, month, day)
       * @param  integer       $month The month to set. (If here NULL is used, the month will be unchanged)
       * @param  integer       $day   The day of the month to set. (If here NULL is used,
       *                              the day of the month will be unchanged)
       */
      public final function setDate( $year = null, $month = null, $day = null )
      {

         if ( \is_array( $year )
              && \count( $year ) > 0 )
         {
            // $year is a array with date informations

            // Change all array keys to lower case
            $array = \array_change_key_case( $year, \CASE_LOWER );

            if ( isset( $array[ 'year' ] ) )
            {
               $this->setDate(
                  \intval( $array[ 'year' ] ),
                  isset( $array[ 'month' ] ) ? \intval( $array[ 'month' ] ) : $this->getMonth(),
                  isset( $array[ 'day' ] ) ? \intval( $array[ 'day' ] ) : $this->getDay()
               );
            }
            else
            {
               $array = \array_values( $array );
               $this->setTime(
                  \intval( $array[ 0 ] ),
                  isset( $array[ 1 ] ) ? \intval( $array[ 1 ] ) : $this->getMonth(),
                  isset( $array[ 2 ] ) ? \intval( $array[ 2 ] ) : $this->getDay()
               );
            }

            return;

         }

         if ( \is_null( $year ) )
         {
            $year = $this->getYear();
         }
         if ( \is_null( $month ) )
         {
            $month = $this->getMonth();
         }
         if ( \is_null( $day ) )
         {
            $day = $this->getDay();
         }

         $this->unixStamp = \mktime(
            $this->getHour(),
            $this->getMinute(),
            $this->getSecond(),
            $month,
            $day,
            $year
         );

      }

      /**
       * Changes the current defined year to defined value.
       *
       * @param  integer $year The new year value. If the value NULL is used it means: Use the year of NOW()
       */
      public final function setYear( $year = null )
      {

         if ( \is_null( $year ) )
         {
            $year = self::CurrentYear();
         }

         $this->unixStamp = \mktime(
            $this->getHour(),
            $this->getMinute(),
            $this->getSecond(),
            $this->getMonth(),
            $this->getDay(),
            $year
         );

      }

      /**
       * Changes the current defined month to defined value.
       *
       * @param  integer $month The new month value. If the value NULL is used it means: Use the month of NOW()
       */
      public final function setMonth( $month = null )
      {

         if ( \is_null( $month ) )
         {
            $month = self::CurrentMonth();
         }

         $this->unixStamp = \mktime(
            $this->getHour(),
            $this->getMinute(),
            $this->getSecond(),
            $month,
            $this->getDay(),
            $this->getYear()
         );

      }

      /**
       * Changes the current defined day to defined value.
       *
       * @param  integer $day The new day value. If the value NULL is used it means: Use the day of NOW()
       */
      public final function setDay( $day = null )
      {

         if ( \is_null( $day ) )
         {
            $day = self::CurrentDay();
         }

         $this->unixStamp = \mktime(
            $this->getHour(),
            $this->getMinute(),
            $this->getSecond(),
            $this->getMonth(),
            $day,
            $this->getYear()
         );

      }

      /**
       * Changes the current defined hour to defined value.
       *
       * @param  integer $hour The new hour value. If the value NULL is used it means: Use the hour of NOW()
       */
      public final function setHour( $hour = null )
      {

         if ( \is_null( $hour ) )
         {
            $hour = self::CurrentHour();
         }

         $this->unixStamp = \mktime(
            $hour,
            $this->getMinute(),
            $this->getSecond(),
            $this->getMonth(),
            $this->getDay(),
            $this->getYear()
         );

      }

      /**
       * Changes the current defined minute to defined value.
       *
       * @param  integer $minute The new minute value. If the value NULL is used it means: Use the minute of NOW()
       */
      public final function setMinute( $minute = null )
      {

         if ( \is_null( $minute ) )
         {
            $minute = self::CurrentMinute();
         }

         $this->unixStamp = \mktime(
            $this->getHour(),
            $minute,
            $this->getSecond(),
            $this->getMonth(),
            $this->getDay(),
            $this->getYear()
         );

      }

      /**
       * Changes the current defined second to defined value.
       *
       * @param  integer $second The new second value. If the value NULL is used it means: Use the second of NOW()
       */
      public final function setSecond( $second = null )
      {

         if ( \is_null( $second ) )
         {
            $second = self::CurrentMinute();
         }

         $this->unixStamp = \mktime(
            $this->getHour(),
            $this->getMinute(),
            $second,
            $this->getMonth(),
            $this->getDay(),
            $this->getYear()
         );

      }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc="A D D   M E T H O D S">

      /**
       * Adds the defined number of seconds.
       *
       * @param  integer $seconds The seconds to add (use a negative value to subtract/remove the seconds)
       * @return \BF\DateTime Returns the current changed instance.
       */
      public final function addSeconds( $seconds = 1 )
      {

         $plus = $seconds > -1 ? '+' : '';
         $name = ( $seconds > 1 || $seconds < -1 ) ? ' seconds' : ' second';

         $this->unixStamp = \strtotime(
            $plus . $seconds . $name,
            $this->unixStamp
         );

         return $this;

      }

      /**
       * Adds the defined number of minutes.
       *
       * @param  integer $minutes The minutes to add (use a negative value to subtract/remove the minutes)
       * @return \BF\DateTime Returns the current changed instance.
       */
      public final function addMinutes( $minutes = 1 )
      {

         $plus = $minutes > -1 ? '+' : '';
         $name = ( $minutes > 1 || $minutes < -1 ) ? ' minutes' : ' minute';

         $this->unixStamp = \strtotime(
            $plus . $minutes . $name,
            $this->unixStamp
         );

         return $this;

      }

      /**
       * Adds the defined number of hours.
       *
       * @param  integer $hours The hours to add (use a negative value to subtract/remove the hours)
       * @return \BF\DateTime Returns the current changed instance.
       */
      public final function addHours( $hours = 1 )
      {

         $plus = $hours > -1 ? '+' : '';
         $name = ( $hours > 1 || $hours < -1 ) ? ' hours' : ' hour';

         $this->unixStamp = \strtotime(
            $plus . $hours . $name,
            $this->unixStamp
         );

         return $this;

      }

      /**
       * Adds the defined number of days.
       *
       * @param  integer $days The days to add (use a negative value to subtract/remove the days)
       * @return \BF\DateTime Returns the current changed instance.
       */
      public final function addDays( $days = 1 )
      {

         $plus = $days > -1 ? '+' : '';
         $name = ( $days > 1 || $days < -1 ) ? ' days' : ' day';

         $this->unixStamp = \strtotime(
            $plus . $days . $name,
            $this->unixStamp
         );

         return $this;

      }

      /**
       * Adds the defined number of weeks.
       *
       * @param  integer $weeks The weeks to add (use a negative value to subtract/remove the weeks)
       * @return \BF\DateTime Returns the current changed instance.
       */
      public final function addWeeks( $weeks = 1 )
      {

         $plus = $weeks > -1 ? '+' : '';
         $name = ( $weeks > 1 || $weeks < -1 ) ? ' weeks' : ' week';

         $this->unixStamp = \strtotime(
            $plus . $weeks . $name,
            $this->unixStamp
         );

         return $this;

      }

      /**
       * Moves the time part to 23:59:59.
       *
       * @return \BF\DateTime Returns the current changed instance.
       */
      public final function moveToEndOfDay()
      {

         $this->setTime( 23, 59, 59 );

         return $this;

      }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc="F O R M A T   M E T H O D S">

      /**
       * Formats the current instance to be usable as a mySql Datetime string. Format is: YYYY-mm-dd HH:MM:SS
       *
       * @return string
       */
      public final function formatMysqlDateTime()
      {
         return \strftime( '%Y-%m-%d %H:%M:%S', $this->unixStamp );
      }

      /**
       * Formats the current instance to be usable as a mySql Date string. Format is: YYYY-mm-dd
       *
       * @return string
       */
      public final function formatMysqlDate()
      {
         return \strftime( '%Y-%m-%d', $this->unixStamp );
      }

      /**
       * Formats the current instance to a named date, depending to current used locale.
       * The output uses long names like  "Montag, 20. Dezember 2030" or short names
       * like "Mon, 20. Dec 2030"
       *
       * @param  boolean $short Return names in short notation?
       * @return string
       */
      public final function formatNamedDate( $short = false )
      {

         if ( \MOD_INTL )
         {

            if ( $short )
            {
               $fmt = \IntlDateFormatter::create(
                  (string) \BF\Locale::GetInstance(),
                  3,
                  3,
                  \date_default_timezone_get(),
                  0,
                  'E, d. MMM y'
               );
            }
            else
            {
               $fmt = \IntlDateFormatter::create(
                  (string) \BF\Locale::GetInstance(),
                  3,
                  3,
                  \date_default_timezone_get(),
                  0,
                  'EEEE, d. MMMM y'
               );
            }

            return $fmt->format( $this->unixStamp );

         }

         if ( $short )
         {
            return sprintf(
               '%s., %s. %s %d',
               $this->getShortWeekDayName(),
               \strftime( '%d', $this->unixStamp ),
               $this->getShortMonthName(),
               $this->getYear()
            );
         }

         return sprintf(
            '%s, %s. %s %d',
            $this->getWeekDayName(),
            \strftime( '%d', $this->unixStamp ),
            $this->getMonthName(),
            $this->getYear()
         );

      }

      /**
       * Formats the current instance to a named date time, depending to current used locale.
       * The output uses long names like  "Montag, 20. Dezember 2030 14:22:47" or short names
       * like "Mon, 20. Dec 2030 21:01"
       *
       * @param  bool $short Kurze Schreibweise für Namen verwenden?
       * @return string
       */
      public final function formatNamedDateTime( $short = false )
      {
         if ( \MOD_INTL )
         {
            if ( $short )
            {
               $fmt = \IntlDateFormatter::create(
                  (string) \BF\Locale::GetInstance(),
                  3,
                  3,
                  \date_default_timezone_get(),
                  0,
                  'E, d. MMM y HH:mm'
               );
            }
            else
            {
               $fmt = \IntlDateFormatter::create(
                  (string) \BF\Locale::GetInstance(),
                  3,
                  3,
                  \date_default_timezone_get(),
                  0,
                  'EEEE, d. MMMM y HH:mm:ss'
               );
            }
            return $fmt->format( $this->unixStamp );
         }
         if ( $short )
         {
            return $this->formatNamedDate( $short )
               . \strftime( ' %H:%M', $this->unixStamp );
         }
         return $this->formatNamedDate( $short )
            . \strftime( ' %H:%M:%S', $this->unixStamp );
      }

      /**
       * Formats the current instance to a RFC 2822 compatible date time with Timezone offset. Usable
       * for example: <b>Thu, 21 Dec 2000 16:01:07 +0200</b>
       *
       * @return string
       */
      public final function formatRfc2822()
      {
         return \date( 'D, d M Y H:i:s O', $this->unixStamp );
      }

      /**
       * Formats the current instance to a W3C compatible date time with Timezone offset. Usable
       * for example: <b>2009-02-27T21:47:11+01:00</b>
       *
       * @return string
       */
      public final function formatW3C()
      {
         return \strftime( '%Y-%m-%dT%H:%M:%S', $this->unixStamp )
            . \date( 'P', $this->unixStamp );
      }

      /**
       * Formats the current instance to a date time, defined by a strftime compatible format string
       *
       * @param  string $format e.g.: %Y-%m-%d %H:%M:%S
       * @return string
       */
      public final function formatWithStrftime( $format )
      {
         if ( \MOD_INTL )
         {
            $format = self::convertStrftimeFormatToIntl( $format );
            $fmt = \IntlDateFormatter::create(
               (string) \BF\Locale::GetInstance(),
               3,
               3,
               \date_default_timezone_get(),
               0,
               $format
            );
            $result = $fmt->format( $this->unixStamp );
            if ( !\BF\str_contains( $result, '%\\' ) )
            {
               return $result;
            }
            return \strftime( \preg_replace( '~%\\([A-Za-z])~', '%$1', $result ), $this->unixStamp );
         }
         return \strftime( $format, $this->unixStamp );
      }

      /**
       * Formats the current instance to a date time, defined by a date() compatible format string
       *
       * @param  string $format
       * @return string
       */
      public final function formatWithDate( $format )
      {
         return \date( $format, $this->unixStamp );
      }

      # </editor-fold>

      /**
       * Magic to string method.
       *
       * @return string
       */
      public function __toString()
      {
         return $this->formatMysqlDateTime();
      }

      /**
       * Compares the current instance with the defined. It returns
       * -1, if $value is higher (newer) than current one, 0 if both
       * datetimes are equal, or 1, if current is higher (newer) than $value.
       *
       * @param  mixed $value
       * @return integer -1, 0, 1 oder (bool)FALSE if comparing fails because $value is of a unusable type
       */
      public function compare( $value )
      {
         if ( $value instanceof \BF\DateTime )
         {
            return (
               ( $value->unixStamp > $this->unixStamp )
                  ? -1
                  : ( ( $value->unixStamp < $this->unixStamp ) ? 1 : 0 )
            );
         }
         if ( $value instanceof \DateTime )
         {
            return (
            ( $value->getTimestamp() > $this->unixStamp )
               ? -1
               : ( ( $value->getTimestamp() < $this->unixStamp ) ? 1 : 0 )
            );
         }
         if ( \is_integer( $value ) )
         {
            return (
               ( $value > $this->unixStamp )
                  ? -1
                  : ( ( $value < $this->unixStamp ) ? 1 : 0 )
            );
         }
         if ( \is_string( $value ) )
         {
            if ( false === ( $dt = self::Parse( $value ) ) )
            {
               return false;
            }
            return (
               ( $dt->unixStamp > $this->unixStamp )
                  ? -1
                  : ( ( $dt->unixStamp < $this->unixStamp ) ? 1 : 0 ) );
         }
         return false;
      }

      /**
       * Nimmt eine andere \BF\DateTime Instanz, ein Integer der einen Unix
       * Timestam definiert oder eine Zeichenkette die eine Datums-Zeitangabe
       * enthält entgegen und vergleicht diese mit der aktuellen Instanz.
       *
       * Wenn der 2. Parameter true ist dann kann nur dann TRUE zurück gegeben
       * werden, wenn diese von Type \BF\DateTime ist und den gleichen Wert
       * (Timestamp) enthält.
       *
       * @param  mixed $value  Wert mit dem die aktuelle Instanz verglichen wird.
       * @param  bool  $strict[=false] Strikte Pruefung nutzen?
       * @return bool  TRUE wenn $value mit der aktuellen Instanz gleich ist.
       */
      public function equals( $value, $strict = false )
      {
         if ( $value instanceof \BF\DateTime )
         {
            return $value->unixStamp == $this->unixStamp;
         }
         if ( $strict )
         {
            return false;
         }
         if ( $value instanceof \DateTime )
         {
            return $value->getTimestamp() == $this->unixStamp;
         }
         if ( \is_integer( $value ) )
         {
            return $value == $this->unixStamp;
         }
         if ( !\is_string( $value ) )
         {
            return false;
         }
         if ( false === ( $dt = self::Parse( $value ) ) )
         {
            return false;
         }
         return $dt->unixStamp == $this->unixStamp;
      }

      /**
       * Initialisiert die Daten der aktuell implementierenden Instanz mit
       * den Daten aus dem übergeben Array. Der einzig notwendige Wert des
       * Array muß unter dem Key 'Stamp' hinterlegt sein und den
       * Unix-Timestamp definieren. Ist der nicht angegeben so müssen mind.
       * die 3 Keys 'Year', 'Month' und 'Day' definiert sein. Optional
       * können noch die Zeitangaben in 'Hour', 'Minute' und 'Second' gesetzt
       * sein.
       *
       * @param  array $objectData Assoziatives Array
       * @return bool
       */
      public function fromArray( array $objectData )
      {
          if ( ! isset ( $objectData['timestamp'] ) )
          {
             return false;
          }
          $this->unixStamp = \intval( $objectData[ 'timestamp' ] );
          return true;
      }

      /**
       * Initialisiert  die Daten dieser Objektinstanz neu aus der
       * übergebenen Zeichenkette, die ein gültiges Datum + opt Zeit und
       * Offset darstellen muss.
       *
       * @param  string $objectString Zeichenkette die die Daten einer
       *                Instanz des implementierenden Objekts entspricht.
       * @return bool
       */
      public function fromString( $objectString )
      {
         if ( false !== ( $stp = \strtotime( $objectString, $this->unixStamp ) ) )
         {
            if ( $stp > 0 )
            {
               $this->unixStamp = $stp;
               return true;
            }
         }
         $search =
             '~(Mon?|Monday|Montag|Tue|Di|Tuesday|Dienstag|Wed|Mi|Wednesd' .
             'ay|Mittwoch|Thu|Do|Thusday|Donnerstag|Fri?|Friday|Freitag|S' .
             'at?|Saturday|Samstag|Sonnabend|Sun|So|Sunday|Sonntag)\b~i';
         $objectString = \preg_replace( $search, '', $objectString );
         if ( false !== ( $stp = \strtotime( $objectString, $this->unixStamp ) ) )
         {
            if ( $stp > 0 )
            {
               $this->unixStamp = $stp;
               return true;
            }
         }
         return false;
      }

      /**
       * Gibt alle Daten der Objektinstanz als assoziatives Array zurück.
       * Keys sind 'timestamp', 'year', 'month', 'day', 'hour', 'minute', 'second'
       *
       * @return array Assoziatives Array
       */
      public function toArray()
      {
         return array(
            'timestamp'  => $this->unixStamp,
            'year'       => $this->getYear(),
            'month'      => $this->getMonth(),
            'day'        => $this->getDay(),
            'hour'       => $this->getHour(),
            'minute'     => $this->getMinute(),
            'second'     => $this->getSecond()
         );
      }

      /**
       * Schreibt alle Daten der Instanz des implementierenden Objekts in den
       * angegebenen XmlWriter. Das Format der zu schreibenden Daten ist
       * natürlich von der Implementierung abhängig!
       *
       * @param \XmlWriter $w Der Writer in den geschrieben werden soll.
       * @param string $elementName[='DateTime']
       */
      public function writeXML( \XmlWriter $w, $elementName = 'DateTime' )
      {
         $w->startElement( $elementName );
         $this->writeXMLAttributes( $w );
         $w->endElement();
      }

      /**
       * @param \XmlWriter $w
       * @param string     $attributeName
       */
      public function writeXMLAttributes( \XmlWriter $w, $attributeName = 'timestamp' )
      {
         $w->writeAttribute( $attributeName, $this->unixStamp );
      }

      /**
       * Liest alle Daten der aktuellen Instanz aus dem angegebenen XmlElement
       *
       * @param  \SimpleXMLElement $xmlElement
       * @return \BF\DateTime
       */
      public function readXML( \SimpleXMLElement $xmlElement )
      {

         $str = \BF\XmlAttributeHelper::GetAttributeValue( $xmlElement, 'timestamp' );

         if ( false === $str )
         {
            $str = \BF\XmlAttributeHelper::GetAttributeValue( $xmlElement, 'datetime' );
         }

         if ( false === $str )
         {
            $str = \BF\XmlAttributeHelper::GetAttributeValue( $xmlElement, 'value' );
         }

         if ( false === $str )
         {
            $str = (string) $xmlElement;
         }

         return $this->fromString( $str );

      }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - -   P U B L I C   S T A T I C   M E T H O D S   - - - - - - - - - - - - - - -">

      /**
       * Parse a datetime string to a \BF\DateTime instance.
       *
       * @param  string $datetimestring
       * @return \BF\DateTime Or boolean FALSE
       */
      public static function Parse( $datetimestring )
      {

         if ( $datetimestring instanceof \BF\DateTime )
         {
            return $datetimestring;
         }

         if ( $datetimestring instanceof \DateTime )
         {
            return new \BF\DateTime( $datetimestring->getTimestamp() );
         }

         if ( ! \is_string( $datetimestring ) )
         {
            $datetimestring = \strval( $datetimestring );
         }

         $dt = new \BF\DateTime();

         if ( ! $dt->fromString( $datetimestring ) )
         {
            return false;
         }

         return $dt;

      }

      /**
       * Gibt zurück wieviele Tage der Monat hat
       *
       * @param  integer $year
       * @param  integer $month
       * @return integer
       */
      public static function GetDaysInMonth( $year, $month )
      {

         $stamp = \mktime( 0, 0, 0, $month, 2, $year );

         return \intval( \date( 't', $stamp ) );

      }

      /**
       * Initialisiert eine neue Instanz anhand der übergebenen Daten.
       *
       * @param  int $year
       * @param  int $month
       * @param  int $day
       * @param  int $hour[=0]
       * @param  int $minute[=0]
       * @param  int $second[=0]
       * @return \BF\DateTime
       */
      public static function Create( $year, $month, $day, $hour = 0, $minute = 0, $second = 0 )
      {
         return new \BF\DateTime(
            \mktime(
               $hour,
               $minute,
               $second,
               $month,
               $day,
               $year
            )
         );
      }

      /**
       * Gibt das aktuelle Jahr zurück
       *
       * @return int
       */
      public static function CurrentYear()
      {
         return \intval( \strftime( '%Y' ) );
      }

      /**
       * Gibt den aktuellen Monat zurück.
       *
       * @return int
       */
      public static function CurrentMonth()
      {
         return \intval( \strftime( '%m' ) );
      }

      /**
       * Gibt den aktuellen Tag des Monats zurück.
       *
       * @return int
       */
      public static function CurrentDay()
      {
         return \intval( \strftime( '%d' ) );
      }

      /**
       * Returns the current hour.
       *
       * @return int
       */
      public static function CurrentHour()
      {
         return \intval( \strftime( '%h' ) );
      }

      /**
       * Returns the current minute.
       *
       * @return int
       */
      public static function CurrentMinute()
      {
         return \intval( \strftime( '%M' ) );
      }

      /**
       * Returns the current second.
       *
       * @return int
       */
      public static function CurrentSecond()
      {
         return \intval( \strftime( '%S' ) );
      }

      /**
       * Gibt das aktuelle Datetime zurück.
       *
       * @return \BF\DateTime
       */
      public static function Now()
      {
         return new \BF\DateTime();
      }

      /**
       * …
       *
       * @param  string $file
       * @return \BF\DateTime
       */
      public static function FromFile( $file )
      {
         return new \BF\DateTime( \filemtime( $file ) );
      }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - -   P R I V A T E   S T A T I C   M E T H O D S   - - - - - - - - - - - - - -">

      /**
       * @param  string format
       * @return string
       */
      private static function convertStrftimeFormatToIntl( $format )
      {
         $replace = array(
            '%C' => 'G',        '%Y' => 'y',        '%y' => 'yy',
            '%m' => 'MM',       '%b' => 'MMM',      '%h' => 'MMM',
            '%B' => 'MMMM',     '%d' => 'dd',       '%j' => 'D',
            '%a' => 'E',        '%A' => 'EEEE',     '%u' => 'e',
            '%p' => 'a',        '%I' => 'hh',       '%H' => 'HH',
            '%M' => 'mm',       '%S' => 'ss',       '%z' => 'zzzz',
            '%Z' => 'zzzz',
            '%c' => '%\\c',     '%D' => 'MM/dd/yy', '%e' => '%\\e',
            '%n' => "\n",       '%t' => "\t",       '%r' => 'hh:mm:ss a',
            '%R' => 'HH:mm:ss', '%T' => 'HH:mm:ss', '%U' => 'w',
            '%V' => 'w',        '%W' => 'w',        '%w' => '%\\w',
            '%x' => '%\\x',     '%X' => '%\\X',     '%%' => '%' );
         $resultStr = '';
         $flen = \strlen( $format );
         for ( $i = 0; $i < $flen; ++$i )
         {
            if ( $format[ $i ] != '%' )
            {
               $resultStr .= $format[ $i ];
               continue;
            }
            if ( $i + 1 >= $flen )
            {
               $resultStr .= $format[ $i ];
               continue;
            }
            $key = \substr( $format, $i, 2 );
            ++$i;
            if ( isset ( $replace[ $key ] ) )
            {
               $resultStr .= $replace[ $key ];
            }
            else
            {
               $resultStr .= $key;
            }
          }
          return $resultStr;
      }

      # </editor-fold>

   }

   # </editor-fold>

   # <editor-fold defaultstate="collapsed" desc="CLASS '\BF\Bundesland">

   /**
    * Diese Klasse stellt ausschliesslich Konstanten bereit, die alle deutschen
    * Bundeslaender definieren.
    *
    * @since  v0.1
    */
   class Bundesland
   {

      # <editor-fold defaultstate="collapsed" desc=" - - >   P R I V A T E   C O N S T R U C T O R   - - - - - - - - - - - - - - - - -">

      private function __construct() { }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - -   C O N S T A N T S   - - - - - - - - - - - - - - - - - - - - - - - - - - -">

      const BADENWUETENBERG = 0;
      const BAYERN = 1;
      const BERLIN = 2;
      const BRANDENBURG = 3;
      const BREMEN = 2;
      const HAMBURG = 2;
      const HESSEN = 6;
      const MECKLENBURG_VORPOMMERN = 3;
      const NIEDERSACHSEN = 2;
      const NORDREIHN_WESTFALEN = 9;
      const RHEINLAND_PFALZ = 9;
      const SAARLAND = 11;
      const SACHSEN = 12;
      const SACHSEN_ANHALT = 13;
      const SCHLESWIG_HOLSTEIN = 2;
      const THUERINGEN = 15;

      # </editor-fold>

   }

   # </editor-fold>

   # <editor-fold defaultstate="collapsed" desc="CLASS '\BF\Feiertage">

   /**
    * Eine Klasse mit statischen Funktionen, die der Ermittlung von Feiertagen dient.
    *
    * @since  v1.0.0
    */
   class Feiertage
   {

      # <editor-fold defaultstate="collapsed" desc=" - - >   P R I V A T E   C O N S T R U C T O R   - - - - - - - - - - - - - - - - -">

      private function __construct() { }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - -   P U B L I C   S T A T I C   M E T H O D S   - - - - - - - - - - - - - - -">

      /**
       * Gibt den Unix Timestamp zurueck, der auf den Tag zeigt auf den der
       * Ostersonntag im angegebenen Jahr faellt. Wird kein Jahr angegeben, so
       * wird das aktuelle Jahr genutzt.
       *
       * Die Berechnung des Ostersonntags erfolgt dabei nach der
       * "Gausschen Gleichung".
       *
       * @param int $jahr Optionale angabe fuers Jahr (4-stellig)
       * @return int Unix-Timestamp fuer Ostersonntag
       */
      public static function OsterSonntag( $jahr=null )
      {
         if ( empty( $jahr ) )
         {
            $jahr = \intval( \strftime( '%Y' ) );
         }
         $a = $jahr % 19;
         $b = $jahr % 4;
         $c = $jahr % 7;
         $m = \number_format( 8 * \number_format( $jahr / 100 ) + 13 ) / 25 - 2;
         $s = \number_format( $jahr / 100 ) - \number_format( $jahr / 400 ) - 2;
         $M = ( 15 + $s - $m ) % 30;
         $N = ( 6 + $s ) % 7;
         $d = ( $M + 19 * $a ) % 30;
         $D = 28;
         if ( $d != 29 )
         {
            if ( $d == 28 && $a >= 11 )
            {
               $D = 27;
            }
            else
            {
               $D = $d;
            }
         }
         $e = ( 2 * $b + 4 * $c + 6 * $D + $N ) % 7;
         return \mktime ( 0, 0, 0, 3, 21, $jahr ) + ( ( $D + $e + 1 ) * 86400 );
      }

      /**
       * Gibt alle Feiertage fuer das angegebene Bundesland und das angebene Jahr
       * zurueck. Wird kein Jahr angegeben so wird das aktuelle Jahr genutzt.
       *
       * Als Wert fuers Bundesland wird eine der Konstanten erwartet die in der
       * Klasse {@see \BF\Bundesland} definiert wurden.
       *
       * @param  int $bundesland Siehe Konstenten aus {@see \BF\Bundesland}
       * @param  int $jahr[=null]
       * @return array Key ist der Timestamp, Value ist der Feitertagsname.
       */
      public static function AlleDeutschen( $bundesland, $jahr=null )
      {
         if ( empty( $jahr ) )
         {
            $jahr = \intval( \strftime( '%Y' ) );
         }
         $ostern = self::OsterSonntag( $jahr );
         $res = array(
             \mktime(0,0,0,1,1,$jahr)   => 'Neujahr',
             ($ostern-172800)           => 'Karfreitag',
             $ostern                    => 'Ostersonntag',
             ($ostern+86400)            => 'Ostermontag',
             \mktime(0,0,0,5,1,$jahr)   => 'Tag der Arbeit',
             \mktime(0,0,0,10,3,$jahr)  => 'Tag der deutschen Einheit',
             \mktime(0,0,0,12,25,$jahr) => '1. Weihnachtstag',
             \mktime(0,0,0,12,26,$jahr) => '2. Weihnachtstag'
         );
         switch ( $bundesland )
         {
            case \BF\Bundesland::BADENWUETENBERG:
               $res[ \mktime( 0, 0, 0, 1, 6,  $jahr ) ] = 'Heilige Drei Koenige';
               $res[ \mktime( 0, 0, 0, 5, 22, $jahr ) ] = 'Fronleichnam';
               $res[ \mktime( 0, 0, 0, 11, 1, $jahr ) ] = 'Allerheiligen';
               return $res;
            case \BF\Bundesland::BAYERN:
               $res[ \mktime( 0, 0, 0, 1, 6,  $jahr ) ] = 'Heilige Drei Koenige';
               $res[ \mktime( 0, 0, 0, 5, 22, $jahr ) ] = 'Fronleichnam';
               $res[ \mktime( 0, 0, 0, 8, 15, $jahr ) ] = 'Mariae Himmelfahrt';
               $res[ \mktime( 0, 0, 0, 11, 1, $jahr ) ] = 'Allerheiligen';
               return $res;
            case \BF\Bundesland::BERLIN:
               return $res;
            case \BF\Bundesland::BRANDENBURG:
               $res[ \mktime( 0, 0, 0, 10, 31, $jahr ) ] = 'Reformationsfest';
               return $res;
            case \BF\Bundesland::HESSEN:
               $res[ \mktime( 0, 0, 0, 5, 22, $jahr )  ] = 'Fronleichnam';
               return $res;
            case \BF\Bundesland::NORDREIHN_WESTFALEN:
               $res[ \mktime( 0, 0, 0, 5, 22, $jahr )  ] = 'Fronleichnam';
               $res[ \mktime( 0, 0, 0, 11, 1, $jahr )  ] = 'Allerheiligen';
               return $res;
            case \BF\Bundesland::SAARLAND:
               $res[ \mktime( 0, 0, 0, 5, 22, $jahr )  ] = 'Fronleichnam';
               $res[ \mktime( 0, 0, 0, 8, 15, $jahr )  ] = 'Mariae Himmelfahrt';
               $res[ \mktime( 0, 0, 0, 11, 1, $jahr )  ] = 'Allerheiligen';
               return $res;
            case \BF\Bundesland::SACHSEN:
               $res[ \mktime( 0, 0, 0, 5, 22, $jahr )  ] = 'Fronleichnam';
               $res[ \mktime( 0, 0, 0, 10, 31, $jahr ) ] = 'Reformationsfest';
               $res[ \mktime( 0, 0, 0, 11, 19, $jahr ) ] = 'Buss- und Bettag';
               return $res;
            case \BF\Bundesland::SACHSEN_ANHALT:
               $res[ \mktime( 0, 0, 0, 1, 6, $jahr )   ] = 'Heilige Drei Koenige';
               $res[ \mktime( 0, 0, 0, 10, 31, $jahr ) ] = 'Reformationsfest';
               return $res;
            case \BF\Bundesland::THUERINGEN:
               $res[ \mktime( 0, 0, 0, 5, 22, $jahr )  ] = 'Fronleichnam';
               $res[ \mktime( 0, 0, 0, 10, 31, $jahr ) ] = 'Reformationsfest';
               return $res;
            default:
               return $res;
         }

      }

      # </editor-fold>

   }

   # </editor-fold>

}

